/*
    socklibpp library
    Copyright (C) 2005  Daniel K. O. <danielosmari at users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


#include <string>
#include <iostream>
#include <algorithm>
#include <functional>
#include <cstdlib>
#include <cstring>

#include <socklibpp/sockaddr.h>


using namespace std;

namespace socklib {

/**     Initializes the family to sock_addr_in::af_inet, the port to 0,
        and the address to sock_addr_in::addr_none.
*/
addr_in::addr_in()
throw() :
        addr(reinterpret_cast<struct sockaddr*>(&_M_addr), sizeof _M_addr)
{
        _M_addr.sin_family = af_inet;
        _M_addr.sin_port = 0;
        uint32_t _temp = addr_none;
        memcpy(&_M_addr.sin_addr, &_temp, 4);
}


/**     \param _addr    A default IPv4 address
        \param _port    A 16-bit IPv4 port
*/
addr_in::addr_in(address _addr, uint16_t _port)
throw() :
        addr(reinterpret_cast<struct sockaddr*>(&_M_addr), sizeof _M_addr)
{
        _M_addr.sin_family = af_inet;
        _M_addr.sin_port = htons(_port);
        uint32_t _temp = htonl(_addr);
        memcpy(&_M_addr.sin_addr, &_temp, 4);
}
        

/**     \param _addr    A 32-bit IPv4 address, in host-byte-order
        \param _port    A 16-bit IPv4 port, in host-byte-order
*/
addr_in::addr_in(uint32_t _addr, uint16_t _port)
throw() :
        addr(reinterpret_cast<struct sockaddr*>(&_M_addr), sizeof _M_addr)
{
        _M_addr.sin_family = af_inet;
        _M_addr.sin_port = htons(_port);
        _addr = htonl(_addr);
        memcpy(&_M_addr.sin_addr, &_addr, 4);
}


/**
        \param _str     A string in the format "X.X.X.X", where "X" is an
                        integer in [0,255]
        \param _port    A 16-bit IPv4 port
*/
addr_in::addr_in(const string& _str, uint16_t _port)
throw (invalid_argument) :
        addr(reinterpret_cast<struct sockaddr*>(&_M_addr), sizeof _M_addr)
{
        _M_addr.sin_family = af_inet;

        if (!validate_addr(_str))
                throw invalid_argument("Invalid address for addr_in");

        uint32_t temp = ::inet_addr(_str.c_str());
        memcpy(&_M_addr.sin_addr, &temp, 4);
        _M_addr.sin_port = htons(_port);
}


/**
        \param _str     A string in the format "X.X.X.X:P", where "X" is an
                        integer in [0,255] and "P" is an integer in [0,65535]
*/
addr_in::addr_in(const string& _str)
throw (invalid_argument) :
        addr(reinterpret_cast<struct sockaddr*>(&_M_addr), sizeof _M_addr)
{
        _M_addr.sin_family = af_inet;

        if (!validate_addr_port(_str))
                throw invalid_argument("Invalid address for addr_in");

        string addrp(_str);
        string::size_type pos = addrp.find(':');

        uint32_t temp = ::inet_addr(addrp.substr(0, pos).c_str());
        memcpy(&_M_addr.sin_addr, &temp, 4);
        
        _M_addr.sin_port = htons(strtoul(addrp.substr(pos+1).c_str(), 0, 10));
}


void
addr_in::reset()
throw()
{
        _M_size = sizeof _M_addr;
}


bool
addr_in::validate_addr(const string& str)
throw()
{
        if (str.empty())
                return false;
                
        // avoid winsock crash
        if (str.find_first_not_of("0123456789.") != string::npos)
                return false;
        
        return inet_addr(str.c_str()) != addr_none;
}


bool
addr_in::validate_addr_port(const string& str)
throw()
{
        if (str.empty())
                return false;
                
        if (str.find_first_not_of("0123456789.:") != string::npos)
                return false;

        string::size_type pos = str.find(':');
        
        if (pos == string::npos)
                return false;
        
        char *e;
        uint32_t port = strtoul(str.c_str()+pos+1, &e, 10);

        if (*e || port>65535)
                return false;
        
        return inet_addr(str.substr(0, pos).c_str()) != addr_none;
}



bool
addr_in::is_ascii_string(const std::string& addrport)
throw()
{
        if (find_if(addrport.begin(), addrport.end(), bind2nd(greater<unsigned char>(), 127))
        != addrport.end())
                return false;

        return true;
}


/**
        This is thread safe; you can call the resolve() method from multiple
        threads without problems.
        
        \param str      A domain name to be resolved

        \return true if the name was resolved successfully

        \note   This method blocks until the resolving operation is done.
*/
bool
addr_in::resolve(const string &str)
throw()
{
        #ifdef WIN32
        // Winsock crashes when resolving non-ascii strings
        if (!is_ascii_string(str))
                return false;

        struct hostent *host;

        if (!( host = gethostbyname( str.c_str() ) ))
                return false;

        memcpy(&_M_addr.sin_addr, host->h_addr_list[0], min(int(host->h_length), size()));

        #else
        // For *nices with GNU libc
        struct hostent hostbuf, *host;
        char extrabuf[1024];
        int err;
        
        if (gethostbyname_r(str.c_str(), &hostbuf, extrabuf, sizeof extrabuf, &host, &err))
                return false;

        memcpy(&_M_addr.sin_addr, host->h_addr_list[0], min(int(host->h_length), size()));

        #endif

        return true;
}


/**
        \param addrport A string in the format "name:port", where port is in
                        [0,65535]

        \return true if the name was resolved successfully and the port is valid

        \note   This method blocks until the resolving operation is done.

        \sa resolve(), port()
*/
bool
addr_in::resolve_port(const string& addrport)
throw()
{
        string::size_type pos = addrport.find(':');
        
        if (pos == string::npos)
                return false;
                
        if (!port(addrport.substr(pos+1)))
                return false;
                
        if (!resolve(addrport.substr(0, pos)))
                return false;

        return true;
}


/**
        \param _addr    A string with the domain name  to be resolved
        \param _port    A 16-bit IPv4 port

        \return true if the name was resolved successfully

        \note   This method blocks until the resolving operation is done.

        \sa resolve(), port()
*/
bool
addr_in::resolve_port(const string& _addr, uint16_t _port)
throw()
{
        port(_port);

        if (!resolve(_addr))
                return false;

        return true;
}


/**
        \param _port    A string representing a port number or a service name
        \param _proto   A string representing a protocol name where the given
                        service is valid
        \return true if the operatin was successful
*/
bool
addr_in::port(const string& _port, const string& _proto)
throw()
{
/* TODO (DanielKO#1#): Check if getservbyname crashes if using non-ascii
                       strings. */
        struct servent *serv = getservbyname(_port.c_str(), _proto.c_str());

        if (!serv) {
                // let's try converting to an integer
                char *e;

                long res = std::strtol(_port.c_str(), &e, 10);

                if (!*e && 0<res && res<65536) {
                        _M_addr.sin_port = res;
                        return true;
                }
                else
                        return false;
        }

        _M_addr.sin_port = serv->s_port;
        return true;
}


/**
        \param _port    A string representing a port number or a service name
        \result true if the operation ws successful
*/
bool
addr_in::port(const string& _port)
throw()
{
/* TODO (DanielKO#1#): Check if getservbyname crashes if using non-ascii
                       strings. */
        struct servent *serv = getservbyname(_port.c_str(), 0);

        if (!serv) {
                // let's try converting to an integer
                char *e;

                long res = std::strtol(_port.c_str(), &e, 10);

                if (!*e && 0<=res && res<65536) {
                        _M_addr.sin_port = htons(res);
                        return true;
                }
                else
                        return false;
        }

        _M_addr.sin_port = serv->s_port;
        return true;
}


/**
        \param _addr    A string in the format "X.X.X.X" where "X" is a number
                        in [0,255]

        \return true if the string is valid
*/
bool
addr_in::ip(const string& _addr)
throw()
{
        if (_addr.empty()) {
                const uint32_t temp = addr_none;
                memcpy(&_M_addr.sin_addr, &temp, 4);
                return true;
        }

        if (!validate_addr(_addr))
                return false;
                
        uint32_t temp = ::inet_addr(_addr.c_str());
        memcpy(&_M_addr.sin_addr, &temp, 4);
                
        return temp != addr_none;
}


/**
        \param _addr    A string in the format "X.X.X.X" where "X" is a number
                        in [0,255]
        \param _port     A 16-bit port

        \return true if the string is valid
        
        \sa ip(), port()
*/
bool
addr_in::ip_port(const std::string& _addr, uint16_t _port)
throw()
{
        return port(_port), ip(_addr);
}


/**
        \param _addrp   A string in the format "X.X.X.X:P" where "X" is a number
                        in [0,255] and "P" is a number in [0,65535]

        \return true if the string is valid

        \sa ip(), port()
*/
bool
addr_in::ip_port(const std::string& _addrp)
throw()
{
        if (!validate_addr_port(_addrp))
                return false;
                
        string::size_type pos = _addrp.find(':');

        return ip(_addrp.substr(0, pos)) && port(_addrp.substr(pos+1));
}


/**     Converts a addr_in object in a string representation, in the form:
        \a X.Y.Z.W:P
*/
ostream& operator<<(ostream& os, const addr_in& si)
{
        string s(inet_ntoa(si._M_addr.sin_addr));
        return os << s << ':' << ntohs(si._M_addr.sin_port);
}


/**     Reads a addr_in object from a string representation, in the form:
        \a X.Y.Z.W:P
*/
std::istream& operator>>(std::istream& is, addr_in& sai)
{
        char ch=0;

        if (!(is >> ch))
                return is;

        if (ch==':') {
                is.putback(':');
                is.clear(ios_base::badbit);
                return is;
        }

        string str(1, ch);

        while (is.get(ch) && ch!=':')
                str.push_back(ch);

        if (!is)
                return is;

        uint16_t port;
        if (! (is >> port) )
                return is;

        if (! sai.ip_port(str, port) )
                is.clear(ios_base::badbit);
        
        return is;
}

}
